/*
 * Copyright 1999,2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.independent.servlet.session.util;

import java.beans.PropertyChangeSupport;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.System;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Random;

/**
 * Minimal implementation of the <b>Manager</b> interface that supports no
 * session persistence or distributable capabilities. This class may be
 * subclassed to create more sophisticated Manager implementations.
 * 
 * @author Craig R. McClanahan
 * @version $Revision: 303151 $ $Date: 2004-08-31 22:07:54 +0800 (星期�?, 31 八月
 *          2004) $
 */

public class ManagerBase {

	public static void main(String[] args) throws Exception {
		ManagerBase ll = new ManagerBase();
		System.out.println(ll.generateSessionId());
	}

	// ----------------------------------------------------- Instance Variables

	protected DataInputStream randomIS = null;
	protected String devRandomSource = "/dev/urandom";

	protected static final String DEFAULT_ALGORITHM = "MD5";

	/**
	 * The message digest algorithm to be used when generating session
	 * identifiers. This must be an algorithm supported by the
	 * <code>java.security.MessageDigest</code> class on your platform.
	 */
	protected String algorithm = DEFAULT_ALGORITHM;

	/**
	 * The Container with which this Manager is associated.
	 */
	// protected Container container;

	/**
	 * Return the MessageDigest implementation to be used when creating session
	 * identifiers.
	 */
	protected MessageDigest digest = null;

	/**
	 * The distributable flag for Sessions created by this Manager. If this flag
	 * is set to <code>true</code>, any user attributes added to a session
	 * controlled by this Manager must be Serializable.
	 */
	protected boolean distributable;

	/**
	 * A String initialization parameter used to increase the entropy of the
	 * initialization of our random number generator.
	 */
	protected String entropy = null;

	/**
	 * The default maximum inactive interval for Sessions created by this
	 * Manager.
	 */
	protected int maxInactiveInterval = 60;

	/**
	 * The session id length of Sessions created by this Manager.
	 */
	protected int sessionIdLength = 16;

	/**
	 * A random number generator to use when generating session identifiers.
	 */
	protected Random random = null;

	/**
	 * The Java class name of the random number generator class to be used when
	 * generating session identifiers.
	 */
	protected String randomClass = "java.security.SecureRandom";

	/**
	 * The set of currently active Sessions for this Manager, keyed by session
	 * identifier.
	 */
	protected HashMap sessions = new HashMap();

	protected PropertyChangeSupport support = new PropertyChangeSupport(this);

	// ------------------------------------------------------------- Security
	// classes
	private class PrivilegedSetRandomFile implements PrivilegedAction {

		public Object run() {
			try {
				File f = new File(devRandomSource);
				if (!f.exists())
					return null;
				randomIS = new DataInputStream(new FileInputStream(f));
				randomIS.readLong();
				return randomIS;
			} catch (IOException ex) {
				return null;
			}
		}
	}

	// ------------------------------------------------------------- Properties

	/**
	 * Return the message digest algorithm for this Manager.
	 */
	public String getAlgorithm() {

		return (this.algorithm);

	}

	/**
	 * Set the message digest algorithm for this Manager.
	 * 
	 * @param algorithm
	 *            The new message digest algorithm
	 */
	public void setAlgorithm(String algorithm) {

		String oldAlgorithm = this.algorithm;
		this.algorithm = algorithm;
		support.firePropertyChange("algorithm", oldAlgorithm, this.algorithm);

	}

	/**
	 * Return the MessageDigest object to be used for calculating session
	 * identifiers. If none has been created yet, initialize one the first time
	 * this method is called.
	 */
	public synchronized MessageDigest getDigest() {

		if (this.digest == null) {
			long t1 = System.currentTimeMillis();
			try {
				this.digest = MessageDigest.getInstance(algorithm);
			} catch (NoSuchAlgorithmException e) {
				try {
					this.digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
				} catch (NoSuchAlgorithmException f) {
					this.digest = null;
				}
			}
			long t2 = System.currentTimeMillis();
		}

		return (this.digest);

	}

	/**
	 * Return the entropy increaser value, or compute a semi-useful value if
	 * this String has not yet been set.
	 */
	public String getEntropy() {

		// Calculate a semi-useful value if this has not been set
		if (this.entropy == null)
			setEntropy(this.toString());

		return (this.entropy);

	}

	/**
	 * Set the entropy increaser value.
	 * 
	 * @param entropy
	 *            The new entropy increaser value
	 */
	public void setEntropy(String entropy) {

		String oldEntropy = entropy;
		this.entropy = entropy;
		support.firePropertyChange("entropy", oldEntropy, this.entropy);

	}

	public String getRandomFile() {
		return devRandomSource;
	}

	/**
	 * Return the random number generator instance we should use for generating
	 * session identifiers. If there is no such generator currently defined,
	 * construct and seed a new one.
	 */
	public synchronized Random getRandom() {
		if (this.random == null) {
			synchronized (this) {
				if (this.random == null) {
					// Calculate the new random number generator seed
					long seed = System.currentTimeMillis();
					long t1 = seed;
					char entropy[] = getEntropy().toCharArray();
					for (int i = 0; i < entropy.length; i++) {
						long update = ((byte) entropy[i]) << ((i % 8) * 8);
						seed ^= update;
					}
					try {
						// Construct and seed a new random number generator
						Class clazz = Class.forName(randomClass);
						this.random = (Random) clazz.newInstance();
						this.random.setSeed(seed);
					} catch (Exception e) {
						this.random = new java.util.Random();
						this.random.setSeed(seed);
					}
					long t2 = System.currentTimeMillis();
				}
			}
		}

		return (this.random);

	}

	/**
	 * Return the random number generator class name.
	 */
	public String getRandomClass() {

		return (this.randomClass);

	}

	/**
	 * Set the random number generator class name.
	 * 
	 * @param randomClass
	 *            The new random number generator class name
	 */
	public void setRandomClass(String randomClass) {

		String oldRandomClass = this.randomClass;
		this.randomClass = randomClass;
		support.firePropertyChange("randomClass", oldRandomClass,
				this.randomClass);

	}

	protected void getRandomBytes(byte bytes[]) {
		if (randomIS != null) {
			try {
				int len = randomIS.read(bytes);
				if (len == bytes.length) {
					return;
				}
			} catch (Exception ex) {
			}
			devRandomSource = null;

			try {
				randomIS.close();
			} catch (Exception e) {
			}

			randomIS = null;
		}
		Random random = getRandom();
		getRandom().nextBytes(bytes);
	}

	/**
	 * Generate and return a new session identifier.
	 */
	public synchronized String generateSessionId() {

		byte random[] = new byte[16];
		String result = null;

		StringBuffer buffer = new StringBuffer();
		do {
			int resultLenBytes = 0;
			if (result != null)
				buffer = new StringBuffer();
			while (resultLenBytes < this.sessionIdLength) {
				getRandomBytes(random);
				random = getDigest().digest(random);
				for (int j = 0; j < random.length
						&& resultLenBytes < this.sessionIdLength; j++) {
					byte b1 = (byte) ((random[j] & 0xf0) >> 4);
					byte b2 = (byte) (random[j] & 0x0f);
					if (b1 < 10)
						buffer.append((char) ('0' + b1));
					else
						buffer.append((char) ('A' + (b1 - 10)));
					if (b2 < 10)
						buffer.append((char) ('0' + b2));
					else
						buffer.append((char) ('A' + (b2 - 10)));
					resultLenBytes++;
				}
			}
			result = buffer.toString();
		} while (sessions.get(result) != null);
		return (result);

	}

}
